#include "ThreadPool.h"
#include "NamedPooledThreadBody.h"

namespace BattleFire {
	static ThreadPool* sThreadPoolSingleton=nullptr;
	ThreadPoolInterface& ThreadPoolInterface::Get()
	{
		return *sThreadPoolSingleton;
	}
	void ThreadPoolInterface::Startup(int32 inThreadsCount)
	{
		new ThreadPool(inThreadsCount);
	}
	void ThreadPoolInterface::Shutdown()
	{
		for (int32 ThreadIndex = (int32)4; ThreadIndex < 46; ThreadIndex++)
		{
			if(sThreadPoolSingleton->mCompositedThreads[ThreadIndex].mIsAttached)
			{
				sThreadPoolSingleton->mCompositedThreads[ThreadIndex].mWorkerThread->Kill();
			}
		}
	}

	BattleFireSInt32 ThreadPool::ThreadIndexToPrioritySetIndex(BattleFireSInt32 inThreadIndex)
	{
		BattleFireSInt32 threadPrioritySetIndex = (inThreadIndex - mNamedThreadCount) / mTaskThreadCountPerSet;
		return threadPrioritySetIndex;
	}
	ThreadPool::ThreadPool(int32 inNonNamedThreadCount)
	{
		mbCreatedHiPriorityThreads = !!bHasHighPriorityThreads;
		mbCreatedBackgroundPriorityThreads = !!bHasBackgroundThreads;

		mLastNamedThread = ENamedThreads::ActualRenderingThread;
		mNamedThreadCount = (int32)mLastNamedThread + 1;
		mTaskThreadSetCount = 1 + (mbCreatedHiPriorityThreads?1:0) + (mbCreatedBackgroundPriorityThreads?1:0);
		mThreadsCount = mNamedThreadCount + inNonNamedThreadCount * mTaskThreadSetCount;
		mTaskThreadCountPerSet = inNonNamedThreadCount;
		
		mPerThreadIDTLSSlot = FPlatformTLS::AllocTlsSlot();

		for (BattleFireSInt32 threadIndex = 0; threadIndex < mThreadsCount; threadIndex++)
		{
			if (threadIndex >= mNamedThreadCount)
			{
				mCompositedThreads[threadIndex].mThreadBody = new NonNamedPooledThreadBody(ThreadIndexToPrioritySetIndex(threadIndex));
			}
			else
			{
				mCompositedThreads[threadIndex].mThreadBody = new NamedPooledThreadBody;
			}
			mCompositedThreads[threadIndex].mThreadBody->Setup(ENamedThreads(threadIndex), mPerThreadIDTLSSlot, &mCompositedThreads[threadIndex]);
		}
		sThreadPoolSingleton = this;
		for (int32 threadIndex = mNamedThreadCount; threadIndex < mThreadsCount; threadIndex++)
		{
			mCompositedThreads[threadIndex].mWorkerThread = WorkerThread::Create("WorkerThread",&ThreadBody(threadIndex));
			mCompositedThreads[threadIndex].mIsAttached = true;
		}
	}

	void ThreadPool::QueueTask(RunnableTask* inRunnableTask, ENamedThreads inThreadToExecuteOn, ENamedThreads inCurrentThreadIfKnown)
	{
		if (GetThreadIndex(inThreadToExecuteOn) == ENamedThreads::AnyThread)
		{
			uint32 targetTaskPriority = GetTaskPriority(inRunnableTask->ThreadToExecuteOn);
			int32 targetThreadPriorityIndex = GetThreadPriorityIndex(inRunnableTask->ThreadToExecuteOn);
			if (targetThreadPriorityIndex == ((int32)EThreadPriorities::BackgroundThreadPriority >> (int32)EThreadPriorities::ThreadPriorityShift))
			{
				if(!mbCreatedBackgroundPriorityThreads)
				{
					targetThreadPriorityIndex = (int32)EThreadPriorities::NormalThreadPriority >> (int32)EThreadPriorities::ThreadPriorityShift;
					targetTaskPriority = (int32)ETaskPriorities::NormalTaskPriority >> (int32)ETaskPriorities::TaskPriorityShift;
				}
			}
			else if (targetThreadPriorityIndex == ((int32)EThreadPriorities::HighThreadPriority >> (int32)EThreadPriorities::ThreadPriorityShift))
			{
				if(!mbCreatedHiPriorityThreads)
				{
					targetThreadPriorityIndex = (int32)EThreadPriorities::NormalThreadPriority >> (int32)EThreadPriorities::ThreadPriorityShift;
					targetTaskPriority = (int32)ETaskPriorities::HighTaskPriority >> (int32)ETaskPriorities::TaskPriorityShift;
				}
			}
			uint32 taskPriorityIndex = targetTaskPriority ? 0 : 1;
			{
				int32 threadToStart = mTasksForNonNamedThreads[targetThreadPriorityIndex].Push(inRunnableTask, taskPriorityIndex);
				if (threadToStart >= 0)
				{
					StartTaskThread(targetThreadPriorityIndex, threadToStart);
				}
			}
		}else{
			ENamedThreads currentThread;
			if (GetThreadIndex(inCurrentThreadIfKnown) == ENamedThreads::AnyThread)
			{
				currentThread = GetCurrentThread();
			}
			else
			{
				currentThread = GetThreadIndex(inCurrentThreadIfKnown);
			}
			{
				int32 QueueToExecuteOn = GetQueueIndex(inThreadToExecuteOn);
				ENamedThreads threadToExecuteOn = GetThreadIndex(inThreadToExecuteOn);
				PooledThreadBody* Target = &ThreadBody((int32)threadToExecuteOn);
				if (threadToExecuteOn == GetThreadIndex(currentThread))
				{
					Target->EnqueueFromThisThread(QueueToExecuteOn, inRunnableTask);
				}
				else
				{
					Target->EnqueueFromOtherThread(QueueToExecuteOn, inRunnableTask);
				}
			}
		}
	}


	int32 ThreadPool::GetNumWorkerThreads()
	{
		int32 Result = (mThreadsCount - mNamedThreadCount) / mTaskThreadSetCount;
		return Result;
	}

	ENamedThreads ThreadPool::GetCurrentThreadIfKnown(bool bLocalQueue)
	{
		ENamedThreads Result = GetCurrentThread();
		if (bLocalQueue && (int32)GetThreadIndex(Result) >= 0 && (int32)GetThreadIndex(Result) < mNamedThreadCount)
		{
			Result = ENamedThreads(int32(Result) | int32(ECommandQueue::LocalQueue));
		}
		return Result;
	}

	bool ThreadPool::IsThreadProcessingTasks(ENamedThreads ThreadToCheck)
	{
		int32 QueueIndex = GetQueueIndex(ThreadToCheck);
		ThreadToCheck = GetThreadIndex(ThreadToCheck);
		return ThreadBody((int32)ThreadToCheck).IsProcessingTasks(QueueIndex);
	}

	void ThreadPool::AttachToThread(ENamedThreads CurrentThread)
	{
		CurrentThread = GetThreadIndex(CurrentThread);
		ThreadBody((int32)CurrentThread).InitializeForCurrentThread();
	}

	uint64 ThreadPool::ProcessThreadUntilIdle(ENamedThreads CurrentThread)
	{
		int32 QueueIndex = GetQueueIndex(CurrentThread);
		CurrentThread = GetThreadIndex(CurrentThread);
		return ThreadBody((int32)CurrentThread).ProcessTasksUntilIdle(QueueIndex);
	}

	void ThreadPool::ProcessThreadUntilRequestReturn(ENamedThreads CurrentThread)
	{
		int32 QueueIndex = GetQueueIndex(CurrentThread);
		CurrentThread = GetThreadIndex(CurrentThread);
		ThreadBody((int32)CurrentThread).ProcessTasksUntilQuit(QueueIndex);
	}

	void ThreadPool::RequestReturn(ENamedThreads CurrentThread)
	{
		int32 QueueIndex = GetQueueIndex(CurrentThread);
		CurrentThread = GetThreadIndex(CurrentThread);
		ThreadBody((int32)CurrentThread).RequestQuit(QueueIndex);
	}

	void ThreadPool::WaitUntilTasksComplete(const RunnableTaskEventArray& Tasks, ENamedThreads CurrentThreadIfKnown)
	{
		ENamedThreads CurrentThread = CurrentThreadIfKnown;
		if (GetThreadIndex(CurrentThreadIfKnown) == ENamedThreads::AnyThread)
		{
			bool bIsHiPri = !!GetTaskPriority(CurrentThreadIfKnown);
			int32 Priority = GetThreadPriorityIndex(CurrentThreadIfKnown);
			CurrentThreadIfKnown = GetThreadIndex(GetCurrentThread());
			CurrentThread = SetPriorities(CurrentThreadIfKnown, Priority, bIsHiPri);
		}
		else
		{
			CurrentThreadIfKnown = GetThreadIndex(CurrentThreadIfKnown);
		}

		if (CurrentThreadIfKnown != ENamedThreads::AnyThread && (int32)CurrentThreadIfKnown < (int32)mNamedThreadCount && !IsThreadProcessingTasks(CurrentThread))
		{
			if (Tasks.size() < 8) // don't bother to check for completion if there are lots of prereqs...too expensive to check
			{
				bool bAnyPending = false;
				for (int32 Index = 0; Index < Tasks.size(); Index++)
				{
					const RunnableTaskEventRef& Task = Tasks[Index];
					if (Task.GetReference() && !Task->IsComplete())
					{
						bAnyPending = true;
						break;
					}
				}
				if (!bAnyPending)
				{
					return;
				}
			}
			// named thread process tasks while we wait
			TRunnableTask<FReturnGraphTask>::CreateTask(&Tasks, CurrentThread).ConstructAndDispatchWhenReady(CurrentThread);
			ProcessThreadUntilRequestReturn(CurrentThread);
		}
		else
		{
			FScopedEvent Event;
			TriggerEventWhenTasksComplete(Event.Get(), Tasks, CurrentThreadIfKnown);
		}
	}

	void ThreadPool::WaitUntilTasksComplete(ENamedThreads CurrentThreadIfKnown)
	{
		ENamedThreads CurrentThread = CurrentThreadIfKnown;
		if (GetThreadIndex(CurrentThreadIfKnown) == ENamedThreads::AnyThread)
		{
			bool bIsHiPri = !!GetTaskPriority(CurrentThreadIfKnown);
			int32 Priority = GetThreadPriorityIndex(CurrentThreadIfKnown);
			CurrentThreadIfKnown = GetThreadIndex(GetCurrentThread());
			CurrentThread = SetPriorities(CurrentThreadIfKnown, Priority, bIsHiPri);
		}
		else
		{
			CurrentThreadIfKnown = GetThreadIndex(CurrentThreadIfKnown);
		}
		FScopedEvent Event;
		TRunnableTask<FinishThreadWorkTask>::CreateTask(nullptr, CurrentThreadIfKnown).ConstructAndDispatchWhenReady(Event.Get(), CurrentThreadIfKnown);
	}

	void ThreadPool::TriggerEventWhenTasksComplete(BattleFireEvent* InEvent, const RunnableTaskEventArray& Tasks, ENamedThreads CurrentThreadIfKnown, ENamedThreads TriggerThread)
	{
		bool bAnyPending = true;
		if (Tasks.size() < 8) // don't bother to check for completion if there are lots of prereqs...too expensive to check
		{
			bAnyPending = false;
			for (int32 Index = 0; Index < Tasks.size(); Index++)
			{
				const RunnableTaskEventRef & Task = Tasks[Index];
				if (Task.mPtr && !Task->IsComplete())
				{
					bAnyPending = true;
					break;
				}
			}
		}
		if (!bAnyPending)
		{
			InEvent->TriggerAll();
			return;
		}
		TRunnableTask<FTriggerEventGraphTask>::CreateTask(&Tasks, CurrentThreadIfKnown).ConstructAndDispatchWhenReady(InEvent, TriggerThread);
	}

	void ThreadPool::AddShutdownCallback(void(*Callback)())
	{
		ShutdownCallbacks.push_back(Callback);
	}

	void ThreadPool::WakeNamedThread(ENamedThreads ThreadToWake)
	{
		const ENamedThreads ThreadIndex = GetThreadIndex(ThreadToWake);
		if ((int32)ThreadIndex < mNamedThreadCount)
		{
			ThreadBody((int32)ThreadIndex).WakeUp(GetQueueIndex(ThreadToWake));
		}
	}

	void ThreadPool::StartTaskThread(int32 Priority, int32 IndexToStart)
	{
		ENamedThreads ThreadToWake = ENamedThreads(IndexToStart + Priority * mTaskThreadCountPerSet + mNamedThreadCount);
		((NonNamedPooledThreadBody&)ThreadBody((int32)ThreadToWake)).WakeUp();
	}
	void ThreadPool::StartAllTaskThreads(bool bDoBackgroundThreads)
	{
		for (int32 Index = 0; Index < GetNumWorkerThreads(); Index++)
		{
			for (int32 Priority = 0; Priority < (int32)EThreadPriorities::NumThreadPriorities; Priority++)
			{
				if (Priority == ((int32)EThreadPriorities::NormalThreadPriority >> (int32)EThreadPriorities::ThreadPriorityShift) ||
					(Priority == ((int32)EThreadPriorities::HighThreadPriority >> (int32)EThreadPriorities::ThreadPriorityShift) && mbCreatedHiPriorityThreads) ||
					(Priority == ((int32)EThreadPriorities::BackgroundThreadPriority >> (int32)EThreadPriorities::ThreadPriorityShift) && mbCreatedBackgroundPriorityThreads && bDoBackgroundThreads)
					)
				{
					StartTaskThread(Priority, Index);
				}
			}
		}
	}

	RunnableTask* ThreadPool::FindWork(ENamedThreads inEThreadName)
	{
		int32 LocalNumWorkingThread = GetNumWorkerThreads();
		int32 MyIndex = int32((uint32(inEThreadName) - mNamedThreadCount) % mTaskThreadCountPerSet);
		int32 Priority = int32((uint32(inEThreadName) - mNamedThreadCount) / mTaskThreadCountPerSet);
		RunnableTask*ret=mTasksForNonNamedThreads[Priority].Pop(MyIndex, true);
		Debug("[%d]ThreadPool::FindWork %p",inEThreadName,ret);
		return ret;
	}
	ENamedThreads ThreadPool::GetCurrentThread()
	{
		ENamedThreads CurrentThreadIfKnown = ENamedThreads::AnyThread;
		CompositedThread* TLSPointer = (CompositedThread*)FPlatformTLS::GetTlsValue(mPerThreadIDTLSSlot);
		if (TLSPointer)
		{
			int32 ThreadIndex = BATTLEFIRE_PTRDIFF_TO_INT32(TLSPointer - mCompositedThreads);
			if (ThreadIndex < mNamedThreadCount)
			{
				CurrentThreadIfKnown = ENamedThreads(ThreadIndex);
			}
			else
			{
				int32 Priority = (ThreadIndex - mNamedThreadCount) / mTaskThreadCountPerSet;
				CurrentThreadIfKnown = SetPriorities(ENamedThreads(ThreadIndex), Priority, false);
			}
		}
		return CurrentThreadIfKnown;
	}

}
